/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, ts, AstTreeUtils } from "arkanalyzer";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { FileMatcher, MatcherCallback, MatcherTypes } from '../../Index';
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { RuleFix } from '../../model/Fix';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoUnnecessaryConditionCheck');
const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: "docs/no-unnecessary-condition-check.md",
    description: "Disallow conditionals where the type is always truthy or always falsy.",
};

interface Options {
    allowConstantLoopConditions?: boolean;
    allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
}

interface LocationInfo {
    fileName: string;
    line: number;
    startCol: number;
    endCol: number;
    start: number;
    end: number;
    nameStr: string;
    description: string;
}

export class NoUnnecessaryConditionCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private defaultOptions: Options = {
        allowConstantLoopConditions: false,
        allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
    };

    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatcherCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        };
        return [fileMatcherCb];
    }

    public check = (arkFile: ArkFile) => {
        if (arkFile instanceof ArkFile) {
            const code = arkFile.getCode();
            if (!code) {
                return;
            }
            const filePath = arkFile.getFilePath();
            const asRoot = AstTreeUtils.getASTNode(arkFile.getName(), code);

            // 检查类型断言
            const locationInfos = this.checkCondition(asRoot, filePath);

            // 输出结果
            locationInfos.forEach(loc => {
                this.addIssueReportNodeFix(loc, filePath);
            });
        }
    }

    private checkCondition(sourceFile: ts.SourceFile, filePath: string): LocationInfo[] {
        const options: Options = this.rule && this.rule.option[0] ? this.rule.option[0] as Options : this.defaultOptions;
        const results: LocationInfo[] = [];

        const visit = (node: ts.Node) => {
            let condition: ts.Expression | undefined;
            if (ts.isIfStatement(node)) {
                condition = node.expression;
                if (ts.isPropertyAccessExpression(condition)) {
                    return;
                } else {
                    this.handleIfStatement(condition, sourceFile, results);
                }
                return;
            } else if (ts.isWhileStatement(node) && !options.allowConstantLoopConditions) {
                condition = node.expression;
            } else if (ts.isDoStatement(node) && !options.allowConstantLoopConditions) {
                condition = node.expression;
            } else if (ts.isForStatement(node) && !options.allowConstantLoopConditions) {
                condition = node.condition;
            } else if (ts.isConditionalExpression(node)) {
                condition = node.condition;
            } else if (ts.isOptionalChain(node)) {
                condition = node.expression;
            } else if (ts.isCallExpression(node)) {
                condition = this.handleCallExpression(node, sourceFile);

            } else if (ts.isBinaryExpression(node)) {
                condition = this.handleBinaryExpression(node, sourceFile, results, options);
            }
            if (condition) {
                this.handleGeneralCondition(condition, sourceFile, node, results);

            }

            ts.forEachChild(node, visit);
        };

        visit(sourceFile);
        return results;
    }

    private handleIfStatement(condition: ts.Expression, sourceFile: ts.SourceFile, results: LocationInfo[]): void {
        if (ts.isPrefixUnaryExpression(condition) && condition.operator === ts.SyntaxKind.ExclamationToken) {
            const operand = condition.operand;
            if (!ts.isLiteralExpression(operand)) {
                return;
            }
            const isAlwaysTruthy = operand.kind === ts.SyntaxKind.TrueKeyword;
            const isAlwaysFalsy = operand.kind === ts.SyntaxKind.FalseKeyword;
            if (isAlwaysTruthy || isAlwaysFalsy) {
                const { line, character } = sourceFile.getLineAndCharacterOfPosition(condition.getStart());
                const assertionName = condition.getText();
                const start = condition.getStart();
                const end = condition.getEnd();
                const endCharacter = character + assertionName.length;
                results.push({
                    fileName: sourceFile.fileName,
                    line: line + 1,
                    startCol: character + 1,
                    endCol: endCharacter + 1,
                    start: start,
                    end: end,
                    nameStr: assertionName,
                    description: 'Unnecessary conditional, value is always truthy.'
                });
            }
        } else {
            if (!ts.isIdentifier(condition)) {
                return;
            }
            const isIdentifierHasNullOrUndefined = this.isIdentifierHasNullOrUndefined(condition, condition.getText(), true);
            if (!isIdentifierHasNullOrUndefined) {
                const { line, character } = sourceFile.getLineAndCharacterOfPosition(condition.getStart());
                const assertionName = condition.getText();
                const start = condition.getStart();
                const end = condition.getEnd();
                const endCharacter = character + assertionName.length;
                results.push({
                    fileName: sourceFile.fileName,
                    line: line + 1,
                    startCol: character + 1,
                    endCol: endCharacter + 1,
                    start: start,
                    end: end,
                    nameStr: assertionName,
                    description: 'Unnecessary optional chain on a non-nullish value.'
                });
            }
        }
    }

    private handleCallExpression(node: ts.CallExpression, sourceFile: ts.SourceFile): ts.Expression | undefined {
        const expression = node.expression;
        const args = node.arguments;
        const arrayPredicate = new Set(['filter', 'find', 'some', 'every']);
        if (ts.isPropertyAccessExpression(expression) && arrayPredicate.has(expression.name.text)) {
            const callback = args[0];
            if (callback && ts.isArrowFunction(callback) && ts.isIdentifier(callback.body)) {
                return callback.body;
            }
        }
        return undefined;
    }

    private handleBinaryExpression(node: ts.BinaryExpression, sourceFile: ts.SourceFile, results: LocationInfo[], options: Options): ts.Expression | undefined {
        if (node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken || node.operatorToken.kind === ts.SyntaxKind.BarBarEqualsToken ||
            node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
            // 检查左侧操作数是否总是为 null 或 undefined
            const isLeftAlwaysNullOrUndefined = this.isAlwaysNullOrUndefined(node.left);
            if (!isLeftAlwaysNullOrUndefined || (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken && isLeftAlwaysNullOrUndefined)) {
                const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.left.getStart());
                const assertionName = node.left.getText();
                const start = node.left.getStart();
                const end = node.left.getEnd();
                const endCharacter = character + assertionName.length;
                results.push({
                    fileName: sourceFile.fileName,
                    line: line + 1,
                    startCol: character + 1,
                    endCol: endCharacter + 1,
                    start: start,
                    end: end,
                    nameStr: assertionName,
                    description: 'Unnecessary optional chain on a non-nullish value.'
                });
            }
            return undefined;
        } else {
            return node;
        }
    }

    private handleGeneralCondition(condition: ts.Expression, sourceFile: ts.SourceFile, node: ts.Node, results: LocationInfo[]): void {
        if (!this.checkConditionType(sourceFile, node, condition)) {
            return;
        }
        let start: number = 0;
        let end: number = 0;
        let position: ts.LineAndCharacter | undefined;
        let assertionName: string = '';
        let description: string = '';
        if (ts.isBinaryExpression(condition)) {
            const isRightAlwaysFalsy = this.isAlwaysTruthyOrFalsy(condition.right);
            if (isRightAlwaysFalsy) {
                position = sourceFile.getLineAndCharacterOfPosition(condition.right.getStart());
                assertionName = condition.right.getText();
                start = condition.right.getStart();
                end = condition.right.getEnd();
                description = 'Unnecessary conditional, value is always falsy.';
            } else {
                position = sourceFile.getLineAndCharacterOfPosition(condition.left.getStart());
                assertionName = condition.left.getText();
                start = condition.left.getStart();
                end = condition.left.getEnd();
                description = 'Unnecessary conditional, value is always truthy.';

            }
        } else if (ts.isOptionalChain(node)) {
            if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node) || ts.isCallExpression(node)) {
                const questionDotToken = node.questionDotToken;
                if (questionDotToken) {
                    start = questionDotToken.getStart();
                    end = questionDotToken.getEnd();
                    position = sourceFile.getLineAndCharacterOfPosition(questionDotToken.getStart());
                    assertionName = questionDotToken.getText();
                    description = 'Unnecessary optional chain on a non-nullish value.';
                }
            }
        } else {
            start = condition.getStart();
            end = condition.getEnd();
            position = sourceFile.getLineAndCharacterOfPosition(condition.getStart());
            assertionName = condition.getText();
            description = 'Unnecessary optional chain on a non-nullish value.';
        }
        this.pushResults(position, assertionName, sourceFile.fileName, start, end, results, description);
    }
    private pushResults(position: ts.LineAndCharacter | undefined,
        assertionName: string, fileName: string, start: number, end: number,
        results: LocationInfo[], description: string): void {
        if (position) {
            const endCharacter = position.character + assertionName.length;
            results.push({
                fileName: fileName,
                line: position.line + 1,
                startCol: position.character + 1,
                endCol: endCharacter + 1,
                start: start,
                end: end,
                nameStr: assertionName,
                description: description
            });
        }
    }

    private isAlwaysTruthyOrFalsy(expression: ts.Expression): boolean {
        // 检查布尔字面量
        if (expression.kind && (expression.kind === ts.SyntaxKind.TrueKeyword || expression.kind === ts.SyntaxKind.FalseKeyword)) {
            return true;
        }

        // 检查数值字面量
        if (ts.isNumericLiteral(expression)) {
            return expression.text !== '0';
        }

        // 检查字符串字面量
        if (ts.isStringLiteral(expression)) {
            return expression.text !== '';
        }

        // 检查 null 和 undefined
        if (this.isAlwaysNullOrUndefined(expression)) {
            return false;
        }

        // 检查对象字面量
        if (ts.isObjectLiteralExpression(expression)) {
            return true;
        }

        // 检查数组字面量
        if (ts.isArrayLiteralExpression(expression)) {
            return true;
        }

        // 检查函数调用
        if (ts.isCallExpression(expression)) {
            return false; // 函数调用的结果无法确定
        }

        // 检查标识符
        if (ts.isIdentifier(expression)) {
            return false; // 标识符的值无法确定
        }

        // 其他情况默认返回 false
        return false;
    }

    private isAlwaysNullOrUndefined(node: ts.Node): boolean {
        // 检查是否为 null 字面量
        if (node.kind === ts.SyntaxKind.NullKeyword) {
            return true;
        }

        // 检查是否为 undefined 字面量
        if (ts.isIdentifier(node) && node.text === 'undefined') {
            return true;
        }

        // 检查是否为布尔字面量 false（在某些情况下可能被视为 'falsy'）
        if (ts.isLiteralExpression(node) &&
            (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword)) {
            return true;
        }

        // 检查是否为标识符
        if (ts.isIdentifier(node)) {
            return this.isIdentifierAlwaysNullOrUndefined(node);
        }

        // 检查是否为二元表达式
        if (ts.isBinaryExpression(node)) {
            // 递归检查二元表达式的操作数
            return this.isAlwaysNullOrUndefined(node.left) && this.isAlwaysNullOrUndefined(node.right);
        }

        // 其他情况默认返回 false
        return false;
    }
    private isIdentifierAlwaysNullOrUndefined(identifier: ts.Identifier): boolean {
        return identifier.text === 'null' || identifier.text === 'undefined';
    }


    private checkConditionType(sourceFile: ts.SourceFile, node: ts.Node, condition: ts.Expression): boolean {
        // 检查布尔字面量
        if (node.kind && (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword)) {
            return true;
        }
        // 检查二元表达式
        if (this.checkConditionTypeBinaryExpression(sourceFile, node, condition)) {
            return true;
        } else if (ts.isPrefixUnaryExpression(condition)) {
            // 检查一元表达式（如 !a）
            if (condition.operator === ts.SyntaxKind.ExclamationToken) {
                return this.checkConditionType(sourceFile, node, condition.operand);
            }
        } else if (ts.isParenthesizedExpression(condition)) {
            // 检查括号表达式 (a)
            return this.checkConditionType(sourceFile, node, condition.expression);
        } else if (ts.isCallExpression(condition)) {
            // 检查函数调用
            return !this.isCallExpressionHasNullOrUndefined(sourceFile, condition.getText());
        } else if (ts.isIdentifier(condition)) {
            // 检查标识符
            const isIdentifierHasNullOrUndefined = this.isIdentifierHasNullOrUndefined(node, condition.getText());
            return !isIdentifierHasNullOrUndefined;
        } else if (ts.isPropertyAccessExpression(condition)) {
            // 检查属性访问表达式（如 obj.prop）
            return this.checkConditionType(sourceFile, node, condition.expression);
        } else if (ts.isElementAccessExpression(condition)) {
            // 检查元素访问表达式（如 arr[index]）
            return this.checkConditionType(sourceFile, node, condition.expression);
        } else if (this.isExplicitType(condition)) {
            // 检查 this 表达式
            return true;
        } else if (ts.isNonNullExpression(condition)) {
            // 检查非空断言表达式（如 a!）
            return true;
        } else if (ts.isLiteralExpression(condition)) {
            // 检查字面量表达式（如字符串、数字等）
            return true;
        }
        return this.checkConditionTypeStatement(sourceFile, node, condition);
    }

    private checkConditionTypeStatement(sourceFile: ts.SourceFile, node: ts.Node, condition: ts.Expression): boolean {
        // 特殊处理 while (true) 和 for (; true; )
        if (ts.isWhileStatement(node)) {
            if (node.expression.kind === ts.SyntaxKind.TrueKeyword) {
                return true;
            }
        } else if (ts.isForStatement(node)) {
            if (node.condition && node.condition.kind === ts.SyntaxKind.TrueKeyword) {
                return true;
            }
        } else if (ts.isDoStatement(node)) {
            if (node.expression && node.expression.kind === ts.SyntaxKind.TrueKeyword) {
                return true;
            }
        }
        // 如果以上都不是，返回 false
        return false;
    }

    private checkConditionTypeBinaryExpression(sourceFile: ts.SourceFile, node: ts.Node, condition: ts.Expression): boolean {
        // 检查二元表达式
        if (ts.isBinaryExpression(condition)) {
            const left = condition.left;
            const right = condition.right;

            if (condition.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
                // && 操作符
                const isLeftAlwaysFalsy = this.checkConditionType(sourceFile, node, left);
                const isRightAlwaysFalsy = this.checkConditionType(sourceFile, node, right);

                if (isLeftAlwaysFalsy || isRightAlwaysFalsy) {
                    return true;
                }
            } else if (condition.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
                // || 操作符
                const isLeftAlwaysTruthy = this.checkConditionType(sourceFile, node, left);
                const isRightAlwaysTruthy = this.checkConditionType(sourceFile, node, right);

                if (isLeftAlwaysTruthy || isRightAlwaysTruthy) {
                    return true;
                }
            } else {
                // 检查是否比较 undefined 或 null
                const isUndefinedComparison = this.isAlwaysNullOrUndefined(left) || this.isAlwaysNullOrUndefined(right);
                const isNullComparison = this.isAlwaysNullOrUndefined(left) || this.isAlwaysNullOrUndefined(right);

                // 检查另一侧是否为明确的类型（如 string、number 等）
                const isExplicitType = this.isExplicitType(left) || this.isExplicitType(right);

                if ((isUndefinedComparison || isNullComparison) && isExplicitType) {
                    return true;
                }
            }
        }
        // 如果以上都不是，返回 false
        return false;
    }

    private isExplicitType(node: ts.Node): boolean {
        if (ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
            return true;
        }
        return false;
    }

    private isCallExpressionHasNullOrUndefined(sourceFile: ts.SourceFile, identifier: string): boolean {
        for (let stmtNode of sourceFile?.statements ?? []) {
            if (!ts.isVariableStatement(stmtNode)) {
                continue;
            }
            let variableList = stmtNode.declarationList;
            if (!variableList || !variableList.declarations) {
                continue;
            }
            for (let declaration of variableList.declarations) {
                if (!ts.isVariableDeclaration(declaration)) {
                    continue;
                }
                let nameIdentifier = declaration.name;
                if (!ts.isIdentifier(nameIdentifier) || !(nameIdentifier.escapedText === identifier)) {
                    continue;
                }
                if (declaration.initializer?.getText().includes('null') || declaration.initializer?.getText().includes('undefined')) {
                    return true;
                }
            }
        }
        return false;
    }

    private isIdentifierHasNullOrUndefined(node: ts.Node, identifier: string, isIf?: boolean): boolean {

        // 遍历父节点语句
        let parentNode = node.parent;
        while (parentNode) {
            // 递归检查每个语句节点
            if (this.checkNode(parentNode, identifier, isIf)) {
                return true;
            }
            parentNode = parentNode.parent;
        }
        return false;
    }
    private checkNode(node: ts.Node, identifier: string, isIf?: boolean): boolean {
        // 如果当前节点是变量声明
        if (ts.isVariableStatement(node) && this.checkNodeVariableStatement(node, identifier)) {
            return true;
        }
        // 如果当前节点是函数声明或方法声明
        if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
            const parameters = node.parameters;
            for (let i = 0; i < parameters.length; i++) {
                if (this.checkNodeFunction(i, parameters, identifier, isIf)) {
                    return true;
                }
            }
        }
        // 如果当前节点是表达式语句
        else if (ts.isExpressionStatement(node)) {
            if (this.isNullOrUndefined(node.expression)) {
                return true;
            }
        }
        // 如果当前节点是赋值表达式
        else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
            if (this.checkNodeIdentifier(node, identifier)) {
                return true;
            }
        }
        // 递归检查子节点
        for (const child of node.getChildren()) {
            this.checkNode(child, identifier, isIf);
        }
        return false;
    }

    private checkNodeVariableStatement(node: ts.VariableStatement, identifier: string): boolean {
        for (const declaration of node.declarationList.declarations) {
            // 检查变量名是否匹配
            if (ts.isIdentifier(declaration.name) && declaration.name.text === identifier) {
                // 检查初始化值是否为 null 或 undefined
                if (declaration.initializer && this.isNullOrUndefined(declaration.initializer)) {
                    return true;
                }
            }
        }
        return false;
    }
    private checkNodeFunction(i: number, parameters: ts.NodeArray<ts.ParameterDeclaration>, identifier: string, isIf?: boolean): boolean {
        const parameter = parameters[i];
        if (parameter.name.getText() === identifier) {
            if (parameter.type) {
                const parameterType = parameter.type?.getText()?.toString();
                if (isIf && (parameterType === 'number' || parameterType === 'string' || parameterType === 'boolean' || parameterType === 'any')) {
                    return true;
                }
                if (!parameterType || parameterType.includes('null') || parameterType.includes('undefined')) {
                    return true;
                }
            }
        }
        return false;
    }

    private checkNodeIdentifier(node: ts.BinaryExpression, identifier: string): boolean {
        // 检查赋值的左侧是否为目标标识符
        if (ts.isIdentifier(node.left) && node.left.text === identifier) {
            // 检查赋值的右侧是否为 null 或 undefined
            if (this.isNullOrUndefined(node.right)) {
                return true;
            }
        }
        return false;
    }

    private isNullOrUndefined(node: ts.Node): boolean {
        // 检查节点是否为 null 或 undefined
        return (ts.isIdentifier(node) && (node.text.includes('null') || node.text.includes('undefined')));
    }

    // 创建修复对象 
    private ruleFix(loc: LocationInfo): RuleFix {
        const [start, end] = this.getFixRange(loc);
        return { range: [start, end], text: '.' };
    }

    // 获取起始位置和结束位置
    private getFixRange(loc: LocationInfo): [number, number] {
        const startPosition = loc.start;
        const endPosition = loc.end;
        return [startPosition, endPosition];
    }

    private addIssueReportNodeFix(loc: LocationInfo, filePath: string): void {
        const severity = this.rule.alert ?? this.metaData.severity;
        if (loc.description) {
            this.metaData.description = loc.description;
        }
        if (loc.nameStr === '?.') {
            let defectFix = new Defects(loc.line, loc.startCol, loc.endCol, this.metaData.description, severity,
                this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, true);
            let fix: RuleFix = this.ruleFix(loc);
            this.issues.push(new IssueReport(defectFix, fix));
            RuleListUtil.push(defectFix);
        } else {
            let defect = new Defects(loc.line, loc.startCol, loc.endCol, this.metaData.description, severity,
                this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
            this.issues.push(new IssueReport(defect, undefined));
            RuleListUtil.push(defect);
        }
    }
}